Skip to main content

Run the app

As usual, let's start the chains and relayer, and then interact with the contracts.

Start Cubist chains and relayer

Now that we've built our contracts, we want to deploy them and then interact with them. We're going to do this locally; the config (cubist-config.json) already specifies the URLs on which the chains will run. The app config will look something like this:

{
...
"network_profiles": {
"default": {
"ethereum": { "url": "http://127.0.0.1:8545" },
"polygon": { "url": "http://127.0.0.1:9545" }
}
}
}

You can alter the config to specify other URLs if you choose.

To start both the local chains and the Cubist relayer, use:

cubist start

Note that this command may take a while (i.e., a minute) the first time you run it. This is because Cubist needs to download and build the local chain running services before it can start the chains.

Once you've run cubist start, you'll see output that looks something like this:

Launching chains
ethereum ✔ [ 0s] [....................] http://localhost:8545/
polygon ✔ [ 1s] [....................] http://localhost:9545/ All servers available
Watching <path>/cubist-deploy dir

This output shows where the localnets are running (e.g., localhost:8545), and how long they took to become initialized and available. The output also lets us know that the local relayer is successfully watching for the events that tell it to relay information from one chain to another.

tip

Alternatively, you can start the chains and the relayer seperately with cubist chains start and cubist relayer start. For more information on these and other Cubist CLI commands, try cubist help, or check out the CLI reference.

caution

Right now, Avalanche and Avalanche-subnet localnets are slower to start up than other networks. We're working on it!

Run the app

Deploy

Let's deploy the contracts and the generated shims:

npm run deploy

> [email protected] deploy
> node ./src/deploy.js

Bridging R1::store (0xaddr@ava_subnet -> 0xaddr@avalanche)
Deployed consumers/receivers
R1 to avalanche @ 0xaddr
R2 to polygon @ 0xaddr
Bridging R2::store (0xaddr@ava_subnet -> 0xaddr@polygon)
Deployed Channel to ava_subnet @ 0xaddr
Deploy producers
Bridging Channel::send (0xaddr@avalanche -> 0xaddr@ava_subnet)
Bridging Channel::send (0xaddr@ethereum -> 0xaddr@ava_subnet)
Deployed producers/senders
S1 to avalanche @ 0xaddr
S2 to ethereum @ 0xaddr

The core of the deploy script that npm run deploy invokes is this simple function (which uses the ORM Cubist generated at build time):

import { CubistORM, } from '../build/orm/index.js';

// Project instance
const cubist = new CubistORM();

// Contract factories
const R1 = cubist.R1;
const R2 = cubist.R2;
const Channel = cubist.Channel;
const S1 = cubist.S1;
const S2 = cubist.S2;


export async function deploy() {
// Deploy both receivers
const r1 = await R1.deploy();
const r2 = await R2.deploy();
...

// Deploy the Channel with the address of both receiver shims
let ch = await Channel.deploy(r1.addressOn(Channel.target()), r2.addressOn(Channel.target()));
...

// Deploy the first sender with the address of the Channel's shim
// on the sender's target (configurable; currently Avalance)
const s1 = await S1.deploy(ch.addressOn(S1.target()));
// Deploy the second sender with the address of the Channel's shim
// on the sender's target (configurable; currently Ethereum)
const s2 = await S2.deploy(ch.addressOn(S2.target()));
...
}

First, the code uses the Cubist instance to deploy both receivers. Then it deploys the Channel:

let ch = await Channel.deploy(r1.addressOn(Channel.target()), r2.addressOn(Channel.target()));

The first constructor argument gets the address of R1 on the Avalanche subnet (since Channel.target() is the Avalanche subnet)---this means the address of the Cubist-generated R1 shim. The second constructor argument does the same, also returning the address of the R2 shim on the subnet. If we'd tried to get the address of S1 on Channel.target, the call would have failed: since the Channel holds no reference to the sender contracts, Cubist never generates an S1 shim on the Avalanche subnet.

Next, the deploy script deploys the sender contracts on their targets, Avalanche and Ethereum:

const s1 = await S1.deploy(ch.addressOn(S1.target()));
const s2 = await S2.deploy(ch.addressOn(S2.target()));

S1's constructor takes the address of the Channel's shim on Avalanche, and S2's constructor takes the address of the Channel's shim on Ethereum.

Interact

Now that we've deployed the contracts, we can use them to send data across the channel, from either sender to:

npm run send 1 5

> [email protected] send
> node ./src/send.js

Sending on channel 1 (0xaddr, avalanche) 5
sending Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
sending R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)
sending R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT Channel::send(5) (0xaddr@avalanche -> 0xaddr@ava_subnet)
SENT R2::store(5) (0xaddr@ava_subnet -> 0xaddr@polygon)
SENT R1::store(5) (0xaddr@ava_subnet -> 0xaddr@avalanche)

This command invokes the send script, which looks like this:

import { CubistORM, } from '../build/orm/index.js';

// Project instance
const cubist = new CubistORM();
const S1 = cubist.S1;
const S2 = cubist.S2;

export async function send(ch, val) {
if (!await cubist.whenBridged()) {
throw new Error('Bridge not running');
}

let sender;
// Choose which sender to use
if (ch === 1) {
// Load from deployment receipt
sender = await S1.deployed();
} else if (ch === 2) {
sender = await S2.deployed();
} else {
throw new Error('Sending channel should be 1 or 2');
}
...
await (await sender.inner.send(val)).wait(/* confirmations: */ 1);
}

The send script takes two command line arguments: (1) the number to be sent and (2) the sender to use, either 1 for S1 or 2 for S2. The send JavaScript function first loads S1 or S2 from its deployment receipt, and then calls the smart contract send function on the correct sender contract.

Shut down

Running cubist stop will shut down both the chains and the relayer. Once the chains and the relayer are shut down, trying to call contract functions from within your dapp will result in errors.